#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <LockInfo/Plugin.h>

#import "FWDBAccess.h"
#import "FWCommon.h"
#import "FWApplication.h"
#import "FWPlugin.h"

static NSString* const kSQLErrorMessage = @"Database - SQL error";


FWDatabase* FWDBConnectDatabase(NSString* dbpath, FWApplication* application)
{
    sqlite3 *database = NULL;
    BOOL debug = application.fwPlugin.isDebugMode;

    if (![[NSFileManager defaultManager] isReadableFileAtPath:dbpath]) {
        [application.fwPlugin setMessageBox:kSQLErrorMessage
                                      line2:@"Cannot find database"
                                     detail:dbpath];

        XLog(@"LI:%@: Failed to find DB at location %@", application.resourceName, dbpath);
        return nil;
    }

#ifdef DEVELOPMENT_MODE
    NSString* tempFile = [NSString stringWithFormat:@"%@/%@.db",
                          [[NSBundle bundleForClass:[application class]] resourcePath], [dbpath lastPathComponent]];
#else
    NSString* tempFile = [NSString stringWithFormat:@"/var/mobile/Library/Caches/com.rja.lockinfo.%@.db",
                          [dbpath lastPathComponent]];
#endif

    NSFileManager* fileMgr = [NSFileManager defaultManager];

    @synchronized(kSQLErrorMessage) {
        // Do we already have a copy of the database we can use?
        if ([fileMgr fileExistsAtPath:tempFile]) {
            NSDate* dbUpdated = [[fileMgr attributesOfItemAtPath:dbpath error:NULL] objectForKey:NSFileModificationDate];
            NSDate* tempUpdated = [[fileMgr attributesOfItemAtPath:tempFile error:NULL] objectForKey:NSFileModificationDate];

            // Is the database copy up to date? If not then remove it
            if ([dbUpdated isLaterThanDate:tempUpdated]) {
                if ([fileMgr removeItemAtPath:tempFile error:nil]) {
                    if ([fileMgr copyItemAtPath:dbpath toPath:tempFile error:nil]) {
                        XLog(@"LI:%@: Copied replacement alternate DB $$$$$$$$$$$; db = %@",
                             application.resourceName, tempFile);
                    }
                    else
                        XLog(@"LI:%@: Unable to copy replacement alternate DB", application.resourceName);
                }
                else
                    XLog(@"LI:%@: Unable to remove expired alternate DB", application.resourceName);
            }
        }
        else {
            // There is no DB copy - create one now
            if ([fileMgr copyItemAtPath:dbpath toPath:tempFile error:nil])
                XLog(@"LI:%@: Copied new alternate DB $$$$$$$$$$$; db = %@", application.resourceName, tempFile);
            else
                XLog(@"LI:%@: Unable to copy new alternate DB", application.resourceName);
        }
            
        if ([fileMgr fileExistsAtPath:tempFile])
            dbpath = tempFile;

        int result = sqlite3_open_v2([dbpath UTF8String], &database, SQLITE_OPEN_READONLY, NULL);
        if (result != SQLITE_OK) {
            [application.fwPlugin setMessageBox:kSQLErrorMessage
                                          line2:application.fwPlugin.isDebugMode ? [NSString stringWithFormat:@"%@ (%d)", FWUFT8StringForCharString(sqlite3_errmsg(database)), result] : FWUFT8StringForCharString(sqlite3_errmsg(database))
                                         detail:dbpath];
            
            XLog(@"LI:%@: Failed to open DB at location %@", application.resourceName, dbpath);
            return nil;
        }
    }
    
    return database;
}

void FWDBDisconnectDatabase(FWDatabase *database)
{
    if (database != NULL)
        sqlite3_close(database);
}
    
    
NSArray* FWDBGetRecordsFromConnection(NSString* sql, NSInteger sqlid, NSObject* context, FWDatabase* database, FWApplication* application, id<FWDBAccessDelegate> delegate, BOOL debug)
{
    // If no connection then do nothing
    if (database == NULL)
        return [NSMutableArray array];
    
    // Setup the SQL Statement and compile it for faster access
    sqlite3_stmt *compiledStatement = NULL;
    
    @try {
        if (sqlite3_prepare_v2(database, [sql UTF8String], -1, &compiledStatement, NULL) != SQLITE_OK) {
            [application.fwPlugin setMessageBox:@"Database - SQL preparation error"
                                          line2:FWUFT8StringForCharString(sqlite3_errmsg(database))
                                         detail:sql];

            XLog(@"LI:%@: **** Failed to prepare statement: %s in %@", application.resourceName, sqlite3_errmsg(database), sql);
            return nil;
        }
        
        XLog(@"LI:%@: Executed SQL %@", application.resourceName, sql);
        
        NSMutableArray* result = [[[NSMutableArray alloc] initWithCapacity:10] autorelease];

        // Loop through the results and add them to the feeds array
        while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
            NSObject* obj = [delegate objectForSQLRecord:compiledStatement 
                                                  withId:sqlid
                                                 context:context
                                             application:application];
            
            if (obj)
                [result addObject:obj];
            else
                break;
        }

        XLog(@"LI:%@: SQL rows returned = %i", application.resourceName, result.count);

        if (debug && (result.count > 0))
            XLog(@"LI:%@: SQL data returned = %@", application.resourceName, result);

        return result;
    }
    @catch (NSException *exception) {
        [application.fwPlugin setMessageBox:@"Database - SQL execution error"
                                      line2:FWUFT8StringForCharString(sqlite3_errmsg(database))
                                     detail:sql];
        
        XLog(@"LI:%@: **** DB Read exception: %@", application.resourceName, exception);
    }
    @finally {			
        if (compiledStatement != NULL)
            sqlite3_finalize(compiledStatement);
    }
    
    return nil;
}

NSArray* FWDBGetSortedRecordsFromConnection(NSString* sql, NSInteger sqlid, NSObject* context, FWDatabase* database, FWApplication* application, id<FWDBAccessDelegate> delegate, NSInteger (*compare)(id, id, void *), BOOL debug)
{
    NSMutableArray* result = (NSMutableArray*)FWDBGetRecordsFromConnection(sql, sqlid, context, database, application, delegate, debug);
    [result sortUsingFunction:compare context:nil];
    return result;
}


NSArray* FWDBGetRecords(NSString* sql, NSInteger sqlid, NSObject* context, NSString* dbpath, FWApplication* application, id<FWDBAccessDelegate> delegate, BOOL debug)
{
    sqlite3 *database = NULL;
    @try {
        database = FWDBConnectDatabase(dbpath, application);
        if (database == NULL)
            return nil;
        
        return FWDBGetRecordsFromConnection(sql, sqlid, context, database, application, delegate, debug);
    }
    @finally {
        if (database != NULL)
            FWDBDisconnectDatabase(database);
    }
    
    return nil;
}

NSArray* FWDBGetSortedRecords(NSString* sql, NSInteger sqlid, NSObject* context, NSString* dbpath, FWApplication* application, id<FWDBAccessDelegate> delegate, NSInteger (*compare)(id, id, void *), BOOL debug)
{
    NSMutableArray* records = (NSMutableArray*)FWDBGetRecords(sql, sqlid, context, dbpath, application, delegate, debug);
    [records sortUsingFunction:compare context:nil];
    
    return records;
}

id FWDBReadColumnBlob(sqlite3_stmt* sqlStatement, int iCol)
{
    int size = sqlite3_column_bytes(sqlStatement, iCol);
    if (size == 0)
        return nil;
    
    const void *ptr = sqlite3_column_blob(sqlStatement, iCol);
    NSData* data = [[NSData alloc] initWithBytes:ptr length:size];
    id decode = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    [data release];
    
    return decode;
}

id FWDBReadColumnBlobWithDefault(sqlite3_stmt* sqlStatement, int iCol, NSObject* def)
{
    id value = FWDBReadColumnBlob(sqlStatement, iCol);
    if (!value)
        return def;
    else
        return value;
}

NSString* FWDBReadColumnString(sqlite3_stmt* sqlStatement, int iCol)
{
    const char* str = (const char*)sqlite3_column_text(sqlStatement, iCol);
    return str ? [NSString stringWithUTF8String:str] : @"";
}

NSArray* FWDBReadColumnBlobToArray(sqlite3_stmt* sqlStatement, int iCol)
{
    NSObject* result = FWDBReadColumnBlob(sqlStatement, iCol);
    if (!result || ![result isKindOfClass:[NSArray class]])
        return [NSArray array];
    
    return (NSArray*)result;
}